interface ComposerArgument {
  name: string;
  is_required: boolean;
  is_array: boolean;
  description: string;
  default: null | string | Array<string>;
}

interface ComposerOption {
  name: string;
  shortcut: string;
  accept_value: boolean;
  is_value_required: boolean;
  is_multiple: boolean; // not supported by fig
  description: string;
  default: null | boolean; // not supported by fig
}

interface ComposerCommandDefinition {
  arguments: Record<string, ComposerArgument>;
  options: Record<string, ComposerOption>;
}

interface ComposerCommand {
  name: string;
  usage: string[]; // this is actually generated by fig
  description: string;
  help: string; //
  definition: ComposerCommandDefinition;
}

interface ComposerListOutput {
  commands: ComposerCommand[];
}

const PACKAGE_REGEXP = new RegExp("^.*/.*$");

const searchGenerator: Fig.Generator = {
  script: function (context) {
    if (context[context.length - 1] === "") return "";
    const searchTerm = context[context.length - 1];
    return `curl -s -H "Accept: application/json" "https://packagist.org/search.json?q=${searchTerm}&per_page=20"`;
  },
  postProcess: function (out) {
    try {
      return JSON.parse(out).results.map(
        (item) =>
          ({
            name: item.name,
            description: item.description,
            icon: "📦",
          } as Fig.Suggestion)
      ) as Fig.Suggestion[];
    } catch (e) {
      return [];
    }
  },
};

// generate package list from composer.json file
const packagesGenerator: Fig.Generator = {
  script: "cat composer.json",
  postProcess: function (out) {
    if (out.trim() == "") {
      return [];
    }

    try {
      const packageContent = JSON.parse(out);
      const dependencies = packageContent["require"] || {};
      const devDependencies = packageContent["require-dev"] || {};

      return filterRealDependencies(
        Object.assign(dependencies, devDependencies)
      ).map((dependencyName) => ({
        name: dependencyName,
        icon: "📦",
      }));
    } catch (e) {
      console.log(e);
    }

    return [];
  },
};

function filterRealDependencies(dependencies) {
  return Object.keys(dependencies).filter((dependency) =>
    dependency.match(PACKAGE_REGEXP)
  );
}

const completionSpec: Fig.Spec = {
  name: "composer",
  description: "Composer Command",

  generateSpec: async (tokens, executeShellCommand) => {
    const jsonList = await executeShellCommand("composer list --format=json");
    const subcommands: Fig.Subcommand[] = [];

    try {
      const data: ComposerListOutput = JSON.parse(jsonList);
      const packagesGeneratorTriggersCommands = ["update", "remove"];

      for (const command of data.commands) {
        subcommands.push({
          name: command.name,
          description: command.description,
          icon: "https://getcomposer.org/img/logo-composer-transparent5.png",

          args: Object.keys(command.definition.arguments).map((argKey) => {
            const arg = command.definition.arguments[argKey];
            const argDefault = arg.default
              ? Array.isArray(arg.default)
                ? arg.default[0]
                : arg.default
              : undefined;

            return {
              name: arg.name,
              description: arg.description,
              isOptional: !arg.is_required,
              default: argDefault,
              isVariadic: arg.is_array,
              generators:
                command.name === "require"
                  ? searchGenerator
                  : packagesGeneratorTriggersCommands.includes(command.name)
                  ? packagesGenerator
                  : [],
            };
          }),

          options: Object.keys(command.definition.options).map((optionKey) => {
            const option = command.definition.options[optionKey];
            const names = [option.name];

            const shortCut = option.shortcut;
            if (shortCut.trim().length > 0) {
              names.push(shortCut);
            }

            return {
              name: names,
              description: option.description,
              isRequired: option.is_value_required,
              args: option.accept_value ? {} : undefined,
            };
          }),
        });
      }
    } catch (err) {
      console.error(err);
    }

    return {
      name: "composer",
      subcommands,
    };
  },
};

export default completionSpec;
